package chagine.core.restful.mvc;

import chagine.core.Env;
import chagine.core.current.*;
import chagine.core.log.OplogWritter;
import chagine.core.util.SpringUtil;
import chagine.redis.cache.OplogCache;
import cn.hutool.extra.servlet.ServletUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * com.seeing.restful.filter.RestfulMvcFilter
 * Created by 王彬安 on 2019/5/11.
 *
 * @author wba
 */
@Slf4j
@WebFilter(filterName = "RestfulMvcFilter", urlPatterns = "/**")
public class RestfulMvcFilter implements Filter {

    private static OplogWritter oplogWritter;

    @Override
    public void init(FilterConfig filterConfig) {
        log.info("==========RestfulMvcFilter===========init begin======================" + oplogWritter);
        ServletContext servletContext = filterConfig.getServletContext();
        ApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
        oplogWritter = ctx.getBean("oplogWritter", OplogWritter.class);
        log.info("==========RestfulMvcFilter===========init end======================" + oplogWritter);
    }

    /**
     * 忽略发些头，不进行打印
     */
    private static final Set<String> IGNORE_PRINT_HEADER = new HashSet<String>() {{
        add("accept");
        add("connection");
        add("basic-agent");
        add("host");
        add("cache-control");
        add("accept-encoding");
        add("content-type");
    }};

    private final static MediaType TEXT = MediaType.valueOf("text/*");
    private final static MediaType JSON = MediaType.valueOf("application/json");

    /**
     * 最大打印的内容长度
     */
    private static final int MAX_PRINT_LENGTH = 4096;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        MutableHttpServletRequest httpServletRequest = new MutableHttpServletRequest((HttpServletRequest) request);

        Enumeration<String> headerNames = httpServletRequest.getHeaderNames();
        Map<String, String> headers = new HashMap<>(8);
        while (headerNames.hasMoreElements()) {
            String name = headerNames.nextElement();
            if (!IGNORE_PRINT_HEADER.contains(name.toLowerCase())) {
                String value = httpServletRequest.getHeader(name);
                headers.put(name, value);
            }
        }

        String clientIp = ServletUtil.getClientIP(httpServletRequest);
        log.info("请求客户端:{} 请求参数头：{}", clientIp, headers);

        String requestBody = null;
        String queryString = null;
        if (HttpMethod.GET.matches(httpServletRequest.getMethod())) {
            queryString = httpServletRequest.getQueryString();
            log.info("请求参数内容：{}", queryString);

        } else if (request.getContentType() != null) {
            MediaType mediaType = MediaType.valueOf(request.getContentType());
            if (TEXT.includes(mediaType) || JSON.includes(mediaType)) {
                requestBody = getRequestBody(httpServletRequest);
                queryString = httpServletRequest.getQueryString();
                log.info("请求报文内容：{},QueryString:{}", requestBody, queryString);
                httpServletRequest.setRequestBody(requestBody);
            } else {
                log.info("请求报文内容格式{}，不打印", request.getContentType());
            }
        } else {
            log.info("请求报文内容格式未知，不打印");
        }

        httpServletRequest.putHeader(Env.CLIENT_IP, clientIp);
        CurrentClientIp.setCurrentClientIp(clientIp);

        boolean useDeferredResult = false;
        try {
            RequestMappingHandlerMapping handlerMapping = (RequestMappingHandlerMapping) SpringUtil.getBean("requestMappingHandlerMapping");
            HandlerExecutionChain handlerChain = handlerMapping.getHandler(httpServletRequest);
            if (handlerChain != null) {
                HandlerMethod handler = (HandlerMethod) handlerChain.getHandler();
                Method method = handler.getMethod();
                if (method.getReturnType() == DeferredResult.class) {
                    useDeferredResult = true;
                }
            }
        } catch (Exception e) {
            log.error("RestfulMvcFilter查询，调用的具体方法异常>>>>>", e);
        }

        if (useDeferredResult) {
            // 如果使用 DeferredResult 不打印日志，走原始过滤器
            chain.doFilter(httpServletRequest, response);
        } else {
            WrapperedHttpServletResponse wrapResponse = new WrapperedHttpServletResponse((HttpServletResponse) response);

            chain.doFilter(httpServletRequest, wrapResponse);

            String respData = null;

            if (wrapResponse.getContentType() != null) {
                MediaType mediaType = MediaType.valueOf(wrapResponse.getContentType());
                if (TEXT.includes(mediaType) || JSON.includes(mediaType)) {
                    byte[] data = wrapResponse.getResponseData();
                    if (data.length > MAX_PRINT_LENGTH) {
                        respData = new String(data, 0, MAX_PRINT_LENGTH, StandardCharsets.UTF_8);
                        log.info("返回报文状态：{}，内容过多只打印前{}：{}", wrapResponse.getStatus(), MAX_PRINT_LENGTH,
                                respData);
                    } else {
                        respData = new String(data, StandardCharsets.UTF_8);
                        log.info("返回报文状态：{}，内容：{}", wrapResponse.getStatus(), respData);
                    }
                } else {
                    log.info("请求报文内容格式{}，不打印", request.getContentType());
                }
            } else {
                log.info("返回报文状态:{}，内容格式未知，不打印", wrapResponse.getStatus());
            }

//        log.info("==================================================oplog begin=============================================");
//        log.info("clientIp:" + clientIp);
//        log.info("respData:" + respData);
//        log.info("requestBody:" + requestBody);
//        log.info("queryString:" + queryString);
//
//        log.info("currentRequestID:" + CurrentRequestID.currentRequestID());
//        log.info("currentTenant:" + CurrentTenant.currentTenant());
//        log.info("currentUser:" + CurrentUser.currentUser());
//        log.info("currentRequestTime:" + CurrentRequestTime.currentRequestTime());
//        log.info(" httpServletRequest.getRequestURI():" + httpServletRequest.getRequestURI());

            OplogCache oplog = new OplogCache();
            oplog.setClientIp(clientIp);
            oplog.setQueryUri(httpServletRequest.getRequestURI());
            oplog.setQueryString(queryString);
            oplog.setRequestBody(requestBody);
            oplog.setResponseData(respData);

            oplog.setRequestId(CurrentRequestID.currentRequestID());
            oplog.setRequestTime(CurrentRequestTime.currentRequestTime());
            oplog.setTenantId(CurrentTenant.currentTenant());
            oplog.setUserId(CurrentUser.currentUser());

            oplogWritter.write(oplog);

            //oplogWritter
            wrapResponse.writeToRealResponse();
        }
    }

    private String getRequestBody(HttpServletRequest req) {
        try {
            return IOUtils.toString(req.getInputStream(), req.getCharacterEncoding());
        } catch (IOException e) {
            log.error("读取请求报文失败", e);
        }
        return null;
    }

    @Override
    public void destroy() {
        System.out.println("oplogWritter = " + oplogWritter);
    }
}
